const fs = require("fs");
const path = require("path");
const Jmeter = require("./jmxModules/Jmeter");
const { Api } = require("../harAnalysis");

class JmxGenerator {

    /**
     * JmxGenerator 类的构造函数，用于初始化 JMX 生成器的各种属性。
     * 
     * @param {{host: string, port: number, db: number}} redis - Redis 配置信息，用于设置全局变量。
     * @param {string} jmxPath - JMX 文件的保存路径。
     * @param {string} testPlanName - 测试计划的名称，用于生成 JMX 文件的文件名。
     * @param {{name: string, value: string}[]} commonHeaders - 通用 Http 请求头信息数组。
     * @param {{name: string, apis: Api[]}[]} groups - API 分组信息数组，每个分组包含名称和 API 列表。
     * @param {{apiId: string, type: string, urlPath: string, name: string, value: string, jsonPath: string, relationships: string[] }[]} variables - 可复用的变量数组，包含不同类型的变量。
     * @param {{redisKey: string, redisVariableNames: string[], jmxVariableNames: string[], isReusable: boolean}[]} inputConfigs - 输入配置信息。
     * @param {{redisKey: string, redisVariableNames: string[], jmxVariableNames: string[]}[]} outputConfigs - 输出配置信息。
     * @param {{urlPath: string, name: string, value: string}[]} customRequestConfigs - 自定义请求配置信息。
     */
    constructor(redis, jmxPath, testPlanName, commonHeaders, groups, variables, inputConfigs, outputConfigs, customRequestConfigs) {
        this.jmxPath = jmxPath;
        this.testPlanName = testPlanName;
        this.commonHeaders = commonHeaders;
        this.globalVariable = [
            { name: "jmxPath", value: "${__BeanShell(import org.apache.jmeter.services.FileServer;FileServer.getFileServer().getBaseDir();)}" },
            { name: "redisHost", value: redis ? redis.host : "" },
            { name: "redisPort", value: redis ? redis.port : "" },
            { name: "redisDb", value: redis ? redis.db : "" }
        ];
        this.groups = groups;
        this.forcedRequestVariables = variables.filter(variable => variable.type === "REQUEST_FORCED");
        this.forcedResponseVariables = variables.filter(variable => variable.type === "RESPONSE_FORCED");
        this.mapRequestVariables = variables.filter(variable => variable.type === "REQUEST_MAP");
        this.mapResponseVariables = variables.filter(variable => variable.type === "RESPONSE_MAP");
        this.generalRequestVariables = variables.filter(variable => variable.type === "REQUEST_GENERAL").map(variable => {
            const customRequestConfig = customRequestConfigs.find(config => config.urlPath === variable.urlPath && config.name === variable.name.replace(/\d+$/, ""));
            if (customRequestConfig) {
                variable.value = customRequestConfig.value;
            }
            return variable;
        });
        this.generalResponseVariables = variables.filter(variable => variable.type === "RESPONSE_GENERAL");
        this.largeValueVariables = variables.filter(variable => variable.type === "LARGE_VALUE");
        this.inputConfigs = inputConfigs;
        this.outputConfigs = outputConfigs;
        this.jmxTxt = "";
        this.guiMode = true;
    }

    setGuiMode(guiMode = false) {
        this.guiMode = guiMode;
    }

    /**
     * 生成 JMX（JMeter 测试计划）文件内容。
     * 该方法通过 Jmeter 类构建一个完整的 JMeter 测试计划，包含全局变量、线程组、接口分析、初始化、主流程、完成阶段等组件，
     * 最后将生成的 JMX 内容存储在实例的 `jmx` 属性中并返回。
     * 
     * @returns {string} 生成的 JMX 文件内容。
     */
    generateJmx() {

        const jmeter = new Jmeter(this.testPlanName);
        const jmxTestPlan = jmeter.testPlan;

        jmeter.configElement.createUserDefinedVariables(jmxTestPlan, { testname: "全局变量", variables: this.globalVariable });

        let jmxThreadGroup = null;
        if (this.guiMode) {
            jmxThreadGroup = jmeter.threads.createThreadGroup(jmxTestPlan, { testname: "线程组", comments: "", threadNumber: "1", loops: "1", duration: "" });
        }
        else {
            jmxThreadGroup = jmeter.threads.createThreadGroup(jmxTestPlan, { testname: "线程组", comments: "", threadNumber: "${__P(threadNumber, 1)}", loops: "-1", duration: "${__P(duration, 30)}" });
        }

        const jmxApiAnalysis = jmeter.logicController.createSimpleController(jmxThreadGroup, { testname: "接口分析", comments: "" });
        if (this.forcedRequestVariables && this.forcedRequestVariables.length > 0) {
            jmeter.configElement.createUserDefinedVariables(jmxApiAnalysis, { testname: "强制请求变量", variables: this.forcedRequestVariables.map(variable => { return { name: variable.name, value: variable.value, desc: `${variable.apiId}: ${String(variable.relationships)}` } }) });
        }
        if (this.forcedResponseVariables && this.forcedResponseVariables.length > 0) {
            jmeter.configElement.createUserDefinedVariables(jmxApiAnalysis, { testname: "强制响应变量", variables: this.forcedResponseVariables.map(variable => { return { name: variable.name, value: `${variable.value}（仅供参考）`, desc: `${variable.apiId}: ${String(variable.relationships)}` } }) });
        }
        if (this.mapRequestVariables && this.mapRequestVariables.length > 0) {
            jmeter.configElement.createUserDefinedVariables(jmxApiAnalysis, { testname: "映射请求变量", variables: this.mapRequestVariables.map(variable => { return { name: variable.name, value: variable.value, desc: `${variable.apiId}: ${String(variable.relationships)}` } }) });
        }
        if (this.mapResponseVariables && this.mapResponseVariables.length > 0) {
            jmeter.configElement.createUserDefinedVariables(jmxApiAnalysis, { testname: "映射响应变量", variables: this.mapResponseVariables.map(variable => { return { name: variable.name, value: `${variable.value}（仅供参考）`, desc: `${variable.apiId}: ${String(variable.relationships)}` } }) });
        }
        if (this.generalRequestVariables && this.generalRequestVariables.length > 0) {
            jmeter.configElement.createUserDefinedVariables(jmxApiAnalysis, { testname: "普通请求变量", variables: this.generalRequestVariables.map(variable => { return { name: variable.name, value: variable.value, desc: `${variable.apiId}: ${String(variable.relationships)}` } }) });
        }
        if (this.generalResponseVariables && this.generalResponseVariables.length > 0) {
            jmeter.configElement.createUserDefinedVariables(jmxApiAnalysis, { testname: "普通响应变量", variables: this.generalResponseVariables.map(variable => { return { name: variable.name, value: `${variable.value}（仅供参考）`, desc: `${variable.apiId}: ${String(variable.relationships)}` } }) });
        }
        if (this.largeValueVariables && this.largeValueVariables.length > 0) {
            jmeter.configElement.createUserDefinedVariables(jmxApiAnalysis, { testname: "大值变量", variables: this.largeValueVariables.map(variable => { return { name: variable.name, value: `size = ${variable.value.length}`, desc: `${variable.apiId}` } }) });
        }
        if (this.inputConfigs.length > 0) {
            this.inputConfigs.forEach(inputConfig => {
                jmeter.configElement.createUserDefinedVariables(jmxApiAnalysis, { testname: `输入变量(${inputConfig.redisKey})`, variables: inputConfig.jmxVariableNames.map((jmxVariableName, index) => { return { name: jmxVariableName, value: "", desc: `redis: ${inputConfig.redisKey}.${inputConfig.redisVariableNames[index]}` } }) });
            });
        }
        if (this.outputConfigs.length > 0) {
            this.outputConfigs.forEach(outputConfig => {
                jmeter.configElement.createUserDefinedVariables(jmxApiAnalysis, { testname: `输出变量(${outputConfig.redisKey})`, variables: outputConfig.jmxVariableNames.map((jmxVariableName, index) => { return { name: jmxVariableName, value: "", desc: `redis: ${outputConfig.redisKey}.${outputConfig.redisVariableNames[index]}` } }) });
            });
        }

        jmeter.custom.createInitialization(jmxThreadGroup, {
            inputs: this.inputConfigs,
            largeValue: this.largeValueVariables && this.largeValueVariables.length > 0 ? { testPlanName: this.testPlanName } : null,
        });

        jmeter.configElement.createHttpHeaderManager(jmxThreadGroup, { testname: "公共请求头", httpHeaders: this.commonHeaders });

        let jmxIfController = null;
        if (this.inputConfigs.length > 0) {
            jmxIfController = jmeter.logicController.createIfController(jmxThreadGroup, {
                testname: "输入变量判断",
                comments: "",
                condition: "${__groovy(" + `${this.inputConfigs.flatMap(config => config.jmxVariableNames).map(variableName => `vars.get("${variableName}") != ""`).join(" && ")}` + ")}"
            });
        }
        const jmxRootController = jmeter.logicController.createTransactionController(this.inputConfigs.length > 0 ? jmxIfController : jmxThreadGroup, { testname: "主流程", comments: "", generateParentSample: true });
        this.groups.forEach(group => {
            const jmxTransactionController = jmeter.logicController.createTransactionController(jmxRootController,
                { testname: group.name, comments: "", generateParentSample: true });
            let asyncCount = 0;
            group.apis.forEach(item => {
                if (item instanceof Array) {
                    /** @type {Api[]} */
                    const subGroup = item;
                    const jmxParallelController = jmeter.logicController.createParallelController(jmxTransactionController,
                        { testname: `${group.name}-异步${String(++asyncCount).padStart(3, "0")}`, comments: "" });
                    subGroup.forEach(api => {
                        const jmxHttpRequest = jmeter.sampler.createHttpRequest(jmxParallelController, {
                            testname: `${api.id} ${api.url.pathname}`,
                            comment: "",
                            protocol: api.url.protocol.replace(":", ""),
                            host: api.url.hostname,
                            port: api.url.port,
                            method: api.method,
                            path: api.url.pathname,
                            request: api.request,
                        });
                        jmeter.assertions.createResponseAssertion(jmxHttpRequest, { testname: "响应断言", subString: '"success":true' });
                        const forcedResponseVariables = this.forcedResponseVariables.filter(variable => variable.apiId === api.id);
                        if (forcedResponseVariables.length > 0) {
                            forcedResponseVariables.forEach(forcedResponseVariable => {
                                if (this.inputConfigs.length > 0 && this.inputConfigs.flatMap(config => config.jmxVariableNames).includes(forcedResponseVariable.name)) {
                                    return;
                                }
                                jmeter.postProcessor.createJsonExtractor(jmxHttpRequest, {
                                    testname: forcedResponseVariable.name,
                                    variableName: forcedResponseVariable.name,
                                    jsonPath: forcedResponseVariable.jsonPath
                                })
                            })
                        }
                        const mapResponseVariables = this.mapResponseVariables.filter(variable => variable.apiId === api.id);
                        if (mapResponseVariables.length > 0) {
                            mapResponseVariables.forEach(mapResponseVariable => {
                                if (this.inputConfigs.length > 0 && this.inputConfigs.flatMap(inputConfig => inputConfig.jmxVariableNames).includes(mapResponseVariable.name)) {
                                    return;
                                }
                                jmeter.postProcessor.createJsonExtractor(jmxHttpRequest, {
                                    testname: mapResponseVariable.name,
                                    variableName: mapResponseVariable.name,
                                    jsonPath: mapResponseVariable.jsonPath
                                }, mapResponseVariable.name);
                            });
                        }
                        const generalResponseVariables = this.generalResponseVariables.filter(variable => variable.apiId === api.id);
                        if (generalResponseVariables.length > 0) {
                            generalResponseVariables.forEach(generalResponseVariable => {
                                if (this.inputConfigs.length > 0 && this.inputConfigs.flatMap(inputConfig => inputConfig.jmxVariableNames).includes(generalResponseVariable.name)) {
                                    return;
                                }
                                jmeter.postProcessor.createJsonExtractor(jmxHttpRequest, {
                                    testname: generalResponseVariable.name,
                                    variableName: generalResponseVariable.name,
                                    jsonPath: generalResponseVariable.jsonPath
                                }, generalResponseVariable.name);
                            });
                        }
                    });
                } else {
                    /** @type {Api} */
                    const api = item;
                    const jmxHttpRequest = jmeter.sampler.createHttpRequest(jmxTransactionController, {
                        testname: `${api.id} ${api.url.pathname}`,
                        comment: "",
                        protocol: api.url.protocol.replace(":", ""),
                        host: api.url.hostname,
                        port: api.url.port,
                        method: api.method,
                        path: api.url.pathname,
                        request: api.request,
                    });
                    jmeter.assertions.createResponseAssertion(jmxHttpRequest, { testname: "响应断言", subString: '"success":true' });
                    const forcedResponseVariables = this.forcedResponseVariables.filter(variable => variable.apiId === api.id);
                    if (forcedResponseVariables.length > 0) {
                        forcedResponseVariables.forEach(forcedResponseVariable => {
                            if (this.inputConfigs.length > 0 && this.inputConfigs.flatMap(inputConfig => inputConfig.jmxVariableNames).includes(forcedResponseVariable.name)) {
                                return;
                            }
                            jmeter.postProcessor.createJsonExtractor(jmxHttpRequest, {
                                testname: forcedResponseVariable.name,
                                variableName: forcedResponseVariable.name,
                                jsonPath: forcedResponseVariable.jsonPath
                            })
                        })
                    }
                    const mapResponseVariables = this.mapResponseVariables.filter(variable => variable.apiId === api.id);
                    if (mapResponseVariables.length > 0) {
                        mapResponseVariables.forEach(mapResponseVariable => {
                            if (this.inputConfigs.length > 0 && this.inputConfigs.flatMap(inputConfig => inputConfig.jmxVariableNames).includes(mapResponseVariable.name)) {
                                return;
                            }
                            jmeter.postProcessor.createJsonExtractor(jmxHttpRequest, {
                                testname: mapResponseVariable.name,
                                variableName: mapResponseVariable.name,
                                jsonPath: mapResponseVariable.jsonPath
                            }, mapResponseVariable.name);
                        });
                    }
                    const generalResponseVariables = this.generalResponseVariables.filter(variable => variable.apiId === api.id);
                    if (generalResponseVariables.length > 0) {
                        generalResponseVariables.forEach(generalResponseVariable => {
                            if (this.inputConfigs.length > 0 && this.inputConfigs.flatMap(inputConfig => inputConfig.jmxVariableNames).includes(generalResponseVariable.name)) {
                                return;
                            }
                            jmeter.postProcessor.createJsonExtractor(jmxHttpRequest, {
                                testname: generalResponseVariable.name,
                                variableName: generalResponseVariable.name,
                                jsonPath: generalResponseVariable.jsonPath
                            }, generalResponseVariable.name);
                        });
                    }
                }
            });
        })

        jmeter.custom.createFinalization(jmxIfController ? jmxIfController : jmxRootController, {
            outputs: this.outputConfigs ? this.outputConfigs : null
        });

        jmeter.listener.createViewResultsTree(jmxTestPlan, { testname: "查看结果树", errorLogging: false, comments: "", filename: "" });
        jmeter.listener.createViewResultsTree(jmxTestPlan, { testname: "查看结果树(异常)", errorLogging: true, comments: "", filename: "" });
        jmeter.listener.createAggregateReport(jmxTestPlan, { testname: "聚合报告" });

        this.jmxTxt = jmeter.toString();
        return this.jmxTxt;
    }

    /**
     * 将生成的 JMX 文件和大值变量文件保存到指定路径。
     * 该方法会检查指定的 JMX 文件存储目录是否存在，如果不存在则递归创建该目录。
     * 然后将生成的 JMX 文件内容写入到对应的文件中。
     * 如果存在大值变量，还会将大值变量信息保存为 JSON 文件。
     * 
     * @param {string} jmxPath - JMX 文件和大值变量文件要保存的目录路径。
     */
    save() {
        if (!fs.existsSync(this.jmxPath)) {
            fs.mkdirSync(this.jmxPath, { recursive: true });
        }
        const jmxFilePath = path.resolve(this.jmxPath, `${this.testPlanName}.jmx`);
        fs.writeFileSync(jmxFilePath, this.jmxTxt);
        if (this.largeValueVariables && this.largeValueVariables.length > 0) {
            const largeValuePath = path.resolve(this.jmxPath, `${this.testPlanName}_largeValue.json`);
            fs.writeFileSync(largeValuePath, JSON.stringify(this.largeValueVariables, null, 4));
        }
    }
}

module.exports = JmxGenerator;